package com.zoe.onelink.logging.trace.dubbo;

import brave.Span;
import brave.internal.Nullable;
import brave.internal.Platform;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.support.RpcUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.util.LinkedHashMap;
import java.util.Map;

public final class DubboParser {
    static final Map<Integer, String> ERROR_CODE_NUMBER_TO_NAME = errorCodeNumberToName();

    /**
     * Dubbo有时会添加错误代码。
     * 例如，他们在过去两年增加了两个。如果我们对每个代码做一个简单的切换/case，比如RpcException。BIZ_EXCEPTION，
     * 在Brave升级之前不会看到新的代码名称。否则我们就只能用密码了直到升级完成。
     * 相反，它使用了反射，这是不太可能失败的，因为Dubbo的错误代码是公共常量。
     */
    static Map<Integer, String> errorCodeNumberToName() {
        Map<Integer, String> result = new LinkedHashMap<>();
        for (Field field : RpcException.class.getDeclaredFields()) {
            if (Modifier.isPublic(field.getModifiers())
                    && Modifier.isStatic(field.getModifiers())
                    && Modifier.isFinal(field.getModifiers())
                    && field.getType() == int.class
            ) {
                try {
                    result.put((Integer) field.get(null), field.getName());
                } catch (Exception e) {
                    // 使所有单元测试失败
                    assert false : e.getMessage();
                    // 我们不使用isAccessible()(已弃用)或canAccess() (Java 9方法)
                    // 读取public常量失败是极端情况，但不会导致任何崩溃
                    // 防止正常的"error"标签工
                    Platform.get().log("Error reading error code %s", field, e);
                }
            }
        }
        return result;
    }

    /**
     * Returns the method name of the invocation or the first string arg of an "$invoke" or
     * "$invokeAsync" method.
     *
     * <p>Like {@link RpcUtils#getMethodName(Invocation)}, except without re-reading fields or
     * returning an unhelpful "$invoke" method name.
     */
    @Nullable
    static String method(Invocation invocation) {
        String methodName = invocation.getMethodName();
        if ("$invoke".equals(methodName) || "$invokeAsync".equals(methodName)) {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 0 && arguments[0] instanceof String) {
                methodName = (String) arguments[0];
            } else {
                methodName = null;
            }
        }
        return methodName != null && !methodName.isEmpty() ? methodName : null;
    }

    /**
     * Returns the {@link URL#getServiceInterface() service interface} of the invoker.
     *
     * <p>This was chosen as the {@link URL#getServiceName() service name} is deprecated for it.
     */
    @Nullable
    static String service(Invoker<?> invoker) {
        URL url = invoker.getUrl();
        if (url == null) {
            return null;
        }
        String service = url.getServiceInterface();
        return service != null && !service.isEmpty() ? service : null;
    }

    static boolean parseRemoteIpAndPort(Span span) {
        RpcContext rpcContext = RpcContext.getContext();
        InetSocketAddress remoteAddress = rpcContext.getRemoteAddress();
        if (remoteAddress == null) {
            return false;
        }
        return span.remoteIpAndPort(
                Platform.get().getHostString(remoteAddress),
                remoteAddress.getPort()
        );
    }

    /**
     * On occasion, (roughly once a year) Dubbo adds more error code numbers. When this occurs, do not
     * use the symbol name, in the switch statement, as it will affect the minimum version.
     */
    @Nullable
    static String errorCode(Throwable error) {
        if (error instanceof RpcException) {
            return ERROR_CODE_NUMBER_TO_NAME.get(((RpcException) error).getCode());
        }
        return null;
    }
}
